#!/usr/bin/env python
# -*- coding: utf-8 -*-


"""
    Part of Seagull - The Pyhon Pages engine
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    renderer_input:
      Input objects for the renderer. An input object contains
      the URL, query string, request headers and other information.
"""
__author__ = 'Jonathan Slenders <jonathan at slenders dot be>'
__license__ = 'GNU LGPL'

import sys
import os
import pickle
import datetime
import threading
import urllib

import utils
import url_translations
from configuration import SystemConfig

################################################################
# Default configuration for render input
################################################################


class DefaultRenderInput(object):
	""" Abstract base class for the configuration """
	def __init__(self):
		self.query_string = '' # e.g.  language=dutch&skin=blue
		self.query_parameters = { } # e.g. { 'language': 'dutch', 'skin' : 'blue' }
		self.post_data = '' # Of type 'str'
		self.request_headers = { } # Request headers
		self.request_method = '' # GET, POST, ...
		self.request_protocol = '' # e.g. HTTP/1.0
		self.headers = { } # Output HTTP headers
		self.http_header_time_format = '%a, %d %b %Y %H:%M:%S %Z GMT' # Should never be changed
		self.client_addr = '' # IP address of the client
		self.cookie = ''
		self.requested_page = u'' # Should be unicode

		self.headers['Content-Type'] = 'application/xhtml+xml';
		self.headers['Date'] = datetime.datetime.now().strftime(self.http_header_time_format)

	def debug_interrupt(self, path, line, column, globals, locals):
		""" This function is called when debug interrupts occur. Not implemented by default """
		pass

	def trace(self, trace_message):
		""" This function is called on trace logging. Not implemented by default """
		pass

	def get_session_id(self):
		# TODO: maybe better place this function in renderer.py, there's no real need to adjust
		# it's behaviour
		""" Start a session """

		def __generate_session_id():
			import time, random
			return time.strftime('%Y%m%d%H%M%S', time.gmtime()) + str(random.randint(0, 1000000))

		# HTTP_COOKIE contains: "var1=value1; var2=value2; session_cookie=2377462378"
		id = None
		for c in self.cookie.replace(' ', '').split(';'):
			if c.find('=') >= 0:
				(key, value) = c.split('=', 1)
				if key == 'session_cookie':
					id = value

		if id != None:
			# Check against cookie injection
			if not id.isalnum():
				id = None

		# Generate new session ID when this is a new session 
		if id == None:
			id = __generate_session_id()

		# Add session cookie header
		self.headers['Set-Cookie'] = 'session_cookie=%s; path=/;' % id

		return id


################################################################
# User defined configurations
################################################################


class StandaloneRenderInput(DefaultRenderInput):
	"""
	Configuration settings, used only when this engine is running
	as a standalone server.
	"""
	def __init__(self, client_addr, method, uri, protocol, request_headers, post_data=None):
		""" All parameters should be a 'str' instance """
		DefaultRenderInput.__init__(self)

		# Grab URL (unicode)
		uri_without_query = urllib.unquote(uri.split('?', 1) [0]).decode('utf-8')

		# Query string (str)
		split = uri.split('?', 1)
		if len(split) > 1:
			self.query_string = split[1]
			self.query_parameters.update(utils.split_query_string(self.query_string))

		self.request_headers = request_headers

		self.client_addr = client_addr
		self.request_headers = request_headers
		self.request_method = method
		self.request_protocol = protocol
		self.post_data = post_data

		if post_data:
			self.query_parameters.update(utils.split_query_string(post_data))

		# Cookie
		if request_headers.has_key('Cookie'):
			self.cookie = request_headers['Cookie']

		# Requested page
		try:
			self.requested_page = url_translations.url_to_page(uri_without_query)
		except:
			self.requested_page = SystemConfig().error_pages[404].page_path


class CGIRenderInput(DefaultRenderInput):
	"""
	Configuration settings, used only when this engine is running
	through CGI
	"""
	def __init__(self):
		DefaultRenderInput.__init__(self)

		# Accept header
		if os.environ.has_key('HTTP_ACCEPT'):
			self.request_headers['Accept'] = os.environ['HTTP_ACCEPT']

		# Content-Type header
		if os.environ.has_key('CONTENT_TYPE'):
			self.request_headers['Content-Type'] = os.environ['CONTENT_TYPE']

		# Content-Length header
		if os.environ.has_key('CONTENT_LENGTH'):
			self.request_headers['Content-Length'] = os.environ['CONTENT_LENGTH']

		# Accept Language header
		if os.environ.has_key('HTTP_ACCEPT_LANGUAGE'):
			self.request_headers['Accept-Language'] = os.environ['HTTP_ACCEPT_LANGUAGE']

		# Client address
		if os.environ.has_key('REMOTE_ADDR'):
			self.client_addr = os.environ['REMOTE_ADDR']

		# Post data
		if self.request_headers.has_key('Content-Type') and self.request_headers['Content-Type'] == 'application/x-www-form-urlencoded':
			input = sys.stdin.read()
			self.post_data = input
			self.query_parameters.update(utils.split_query_string(input))

		# Cookie
		if os.environ.has_key('HTTP_COOKIE'):
			self.cookie = os.environ['HTTP_COOKIE']

		# Requested page
		if os.environ.has_key ('REDIRECT_URL'):
			try:
				self.requested_page = url_translations.url_to_page(
					os.environ['REDIRECT_URL'].decode('utf-8'))
			except:
				self.requested_page = SystemConfig().error_pages[404].page_path
		else:
			self.requested_page = SystemConfig().error_pages[404].page_path

		# Query string (str)
		if os.environ.has_key('QUERY_STRING'):
			self.query_string = os.environ['QUERY_STRING'] # TODO: should we decode to unicode or not?
			self.query_parameters.update(utils.split_query_string(os.environ['QUERY_STRING']))

class SimpleRenderInput(DefaultRenderInput):
	"""
	When a page is rendered internally through reflexion,
	only 
	"""
	def __init__(self, page, query_parameters):
		DefaultRenderInput.__init__(self)
		self.requested_page = page
		self.query_parameters = query_parameters
